using System;
using System.Net.Sockets;
using System.IO;
using Servlets.Http;
using System.Net;
using System.Reflection;
using System.Linq;
using System.Collections.Generic;
using Servlets.Attributes;
using Servlets.Exceptions;
using Servlets.Utils;
using Servlets.Api;
using TemplateCompiler;
using System.CodeDom.Compiler;

namespace Servlets.Server {
	class Server : IDisposable {
		private static Config _config = new Config("config.properties");
		public static Config Config {get{return _config;}}
		
		public static readonly string SERVLET_FOLDER;
		public static readonly string VIEW_FOLDER;
		public static readonly string STATIC_ROOT;
		
		private bool running = false;
		private IListener listener;
		private Mapping mapping;
		
		int port;
		
		static Server() {
			Session.SessionLengthInDays = Config.SessionLengthInDays;
			SERVLET_FOLDER = Config.ServletFolder;
			VIEW_FOLDER = Config.ViewFolder;
			STATIC_ROOT = Config.StaticRoot;
		}
		
		public Server(int port) : this(IPAddress.Parse("127.0.0.1"), port) {}
		
		public Server(IPAddress address, int port) {
			this.port = port;
			listener = new Servlets.Sockets.TcpListener(address, port);
			mapping = new Mapping();
			LoadServlets();
			LoadMilkFiles();
		}
		
		private void LoadServlets() {
			string[] servletDlls = GetAllServletFiles();
			foreach(string file in servletDlls) {
				Assembly assembly = Assembly.LoadFrom(file);
				LoadServlets(assembly);
			}
		}
		
		private void LoadServlets(Assembly assembly) {
			Log.WriteLine("[INFO] Loading Servlets from " + assembly.GetName());
			Type[] types = GetServletsFromAssembly(assembly).ToArray();
			//var alltypes = assembly.GetTypes().ToArray();
			//var allattributes = alltypes.First().GetCustomAttributes(true);
			mapping.AddServlets(types);
		}
		
		private void LoadMilkFiles() {
			string[] milkFiles = GetAllMilkFiles();
			foreach(string milkFile in milkFiles) {
				LoadMilkFile(milkFile);
			}
		}
		
		private void LoadMilkFile(string milkFile) {
			string[] servletDlls = GetAllServletFiles();
			Compiler compiler = new Compiler();
			compiler.Compile(milkFile, servletDlls);
			Assembly assembly = compiler.OutputtedAssembly();
			if(assembly != null) {
				LoadServlets(assembly);
			} else {
				Log.WriteLine("[ERROR] '" + milkFile + "' had compile errors");
				CompilerResults errors = compiler.GetCompilerResults();
				foreach(CompilerError error in errors.Errors) {
					Log.WriteLine("[ERROR] Line " +error.Line + ": " + error.ErrorText);
				}
			}
		}
		
		private string[] GetAllServletFiles() {
			if(!Directory.Exists(SERVLET_FOLDER)) {
				Log.WriteLine("[WARN] Servlet folder `" + SERVLET_FOLDER + "` not found.");
				return new string[] {};
			}
			Log.WriteLine("[INFO] Loading servlets from '" + SERVLET_FOLDER + "'");
			IEnumerable<string> files = AllFilesTypeInFolder("*.dll", SERVLET_FOLDER);
			return files.ToArray();
		}
		
		private string[] GetAllMilkFiles() {
			if(!Directory.Exists(VIEW_FOLDER)) {
				Log.WriteLine("[WARN] View folder `" + VIEW_FOLDER + "` not found.");
				return new string[] {};
			}
			Log.WriteLine("[INFO] Loading views from '" + VIEW_FOLDER + "'");
			IEnumerable<string> files = AllFilesTypeInFolder("*.milk", VIEW_FOLDER);
			return files.ToArray();
		}
		
		private IEnumerable<string> AllFilesTypeInFolder(string fileExtension, string folder) {
			string[] directories = Directory.GetDirectories(folder);
			foreach(string d in directories) {
				foreach(string f in AllFilesTypeInFolder(fileExtension, d)) {
					yield return f;
				}
			}
			
			IEnumerable<string> files = Directory.GetFiles(folder, fileExtension);
			foreach(string f in files) {
				yield return f;
			}
		}
		
		private IEnumerable<Type> GetServletsFromAssembly(Assembly assembly)
		{
			Type[] types = assembly.GetTypes();
			foreach(Type type in types)
			{
				if(type.GetCustomAttributes(typeof(ServletAttribute), true).Length > 0)
				{
					yield return type;
				}
			}
		}
		
		public void Start() {
			if(running) {
				return;
			}
			
			running = true;
			
			while(running) {
				Log.WriteLine("[INFO] Listening port:" + port);
				listener.Start();
				IClient client = listener.AcceptClient();
				RequestDispatcher rd = new RequestDispatcher(mapping);
				Action<IClient> handler = rd.HandleRequest;
				handler.BeginInvoke(client, null, null);
			}
		}
		
		public void Stop() {
			running = false;
		}
		
		public void Dispose() {
			Log.WriteLine("[INFO] Disposing");
			this.Stop();
			listener.Dispose();
		}
	}
}

